#pragma once

#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <future>
#include <memory>
#include <condition_variable>
#include <functional>

#include "./ring_buffer.h"


namespace lurpc {

class thread_pool {
public:
    thread_pool();
    thread_pool(int, int);
    thread_pool(const thread_pool&) = delete;
    thread_pool(thread_pool&&) = delete;

public:
    ~thread_pool();

public:
    thread_pool& operator = (const thread_pool&) = delete;
    thread_pool& operator = (thread_pool&&) = delete;

public:
    template<typename Fn, typename ...Args>
    auto submit(Fn&& __f_, Args&&... __args_) -> std::future<decltype(__f_(__args_...))>;

private:
    std::vector<std::thread> __ts_;
    bool __shutdown_;
    int __size_;
    std::queue<std::packaged_task<void()>> __task_que_;
    int __que_sz_;
    std::mutex __que_mut_;
    std::condition_variable __cv_;

};


thread_pool::thread_pool() : thread_pool(0, 0) { }

thread_pool::thread_pool(int _pool_sz, int _que_sz) : __shutdown_(false), __size_(_pool_sz), __que_sz_(_que_sz) {
    for (auto i = 0; i < __size_; ++i) {
        __ts_.emplace_back([this]() {
            while (true) {
                std::unique_lock<std::mutex> _ul(__que_mut_);
                __cv_.wait(_ul, [this]() { return __shutdown_ || !__task_que_.empty(); });

                if (__shutdown_ && __task_que_.empty()) {
                    break;
                }

                if (!__task_que_.empty()) {
                    auto _task = std::move(__task_que_.front());
                    __task_que_.pop();
                    _ul.unlock();
                    _task();
                }
            }
        });
    }
}

thread_pool::~thread_pool() {
    __que_mut_.lock();
    __shutdown_ = true;
    __que_mut_.unlock();
    __cv_.notify_all();
    for (auto& _t : __ts_) {
        _t.join();
    }
}

template<typename Fn, typename... Args>
inline auto thread_pool::submit(Fn&& __f_, Args&&... __args_) -> std::future<decltype(__f_(__args_...))> {
    using _Rt = decltype(__f_(__args_...));

    auto _task = std::make_shared<std::packaged_task<_Rt()>>(
            std::bind(
                    std::forward<Fn>(__f_),
                    std::forward<Args>(__args_)...
            )
    );

    auto fut = _task->get_future();

    __que_mut_.lock();
    if (__shutdown_ || __task_que_.size() >= __que_sz_) {
        __que_mut_.unlock();
        throw std::runtime_error("submit task after thread pool shutdown");
    }
    __task_que_.emplace([_task]() {
        (*_task)();
    });
    __que_mut_.unlock();

    __cv_.notify_one();
    return fut;
}

}
